// i.r(t),
//   i.d(t, {
//     TRANSITION_DISTANCE_MULTIPLIER: () => S,
//     baseTransitionSpeedKey: () => E,
//     default: () => CameraModule
//   })

import * as THREE from "three"
import r from "../math/69626"
import Module from "./Module"
import CameraData, { copyData } from "../data/CameraData"
import Message from "../message/message"
class CameraMoveMessage extends Message {
  from: any
  to: any
  timestamp: number
  constructor(e, t) {
    super(), (this.from = t), (this.to = e), (this.timestamp = Date.now())
  }
}
class BeginCameraMoveMessage extends CameraMoveMessage {}
class EndCameraMoveMessage extends CameraMoveMessage {}
import OpenDeferred from "../utils/OpenDeferred"
import d from "../math/79437"
import SettingsData from "../data/SettingsData"
import CanvasData from "../data/CanvasData"
import transitionTypeEnum from "../enum/transitionType.enum"
import { ResetPitchCommand } from "../command/camera.command"
import configConst from "../constant/config.const"
import keyConst from "../constant/key.const"
import funcUtil from "../utils/func.util"
import { PlayerResizedMessage } from "../message/play.message"
import { NullGeneratorResult } from "../engine/EngineGenerators"
import EngineContext from "../EngineContext"
import MarketContext from "../MarketContext"
const b = {
  [transitionTypeEnum.Instant]: { blackoutTime: 0, transitionTime: 0 },
  [transitionTypeEnum.FadeToBlack]: {
    blackoutTime: configConst.transitionConfig.camera.transitionBlackoutTime,
    transitionTime: 2 * configConst.transitionConfig.camera.transitionBlackoutTime
  },
  [transitionTypeEnum.Interpolate]: {
    blackoutTime: 0,
    transitionTime: configConst.transitionConfig.camera.transitionFadeTime
  },
  [transitionTypeEnum.MoveToBlack]: {
    blackoutTime: configConst.transitionConfig.camera.transitionBlackoutTime,
    transitionTime: configConst.transitionConfig.camera.transitionFadeTime
  }
}

export default class CameraModule extends Module {
  cameraData: CameraData
  eulerCache: THREE.Euler
  quaternionCache: THREE.Quaternion
  engine: EngineContext
  market: MarketContext
  canvas: CanvasData
  baseTransitionTime: any
  transitionSpeed: any
  constructor() {
    super(...arguments)
    this.name = "camera"
    this.eulerCache = new THREE.Euler()
    this.quaternionCache = new THREE.Quaternion()
  }
  async init(e, t) {
    this.engine = t
    this.market = t.market
    this.canvas = await this.market.waitForData(CanvasData)
    this.cameraData = new CameraData(this.canvas.width, this.canvas.height)
    this.market.register(this, CameraData, this.cameraData)
    this.baseTransitionTime = e.baseTransitionTime || configConst.transitionConfig.camera.baseTransitionTime
    this.market.waitForData(SettingsData).then(e => {
      e.onPropertyChanged(keyConst.baseTransitionSpeedKey, e => {
        this.baseTransitionTime = e
      })
    })
    t.commandBinder.addBinding(ResetPitchCommand, async () => this.resetPitch())
    this.bindings.push(t.subscribe(PlayerResizedMessage, e => this.cameraData.setCameraDimensions(e.width, e.height)))
    this.transitionSpeed = e.transitionSpeed || configConst.transitionConfig.camera.transitionSpeed
  }
  getData(e = !0) {
    return e ? Object.freeze(this.cameraData.clone()) : this.cameraData
  }
  onUpdate(e) {}
  fromPose() {
    return {
      position: this.cameraData.pose.position.clone(),
      rotation: this.cameraData.pose.rotation.clone(),
      projection: this.cameraData.pose.projection.clone(),
      focalDistance: this.cameraData.pose.focalDistance
    }
  }
  updateCameraProjection(e) {
    e && (this.cameraData.pose.projection.copy(e), this.cameraData.pose.commit(), this.broadcastCameraMoved({ projection: e }))
  }
  updateCameraFocus(e) {
    ;(this.cameraData.pose.focalDistance = e), this.cameraData.pose.commit()
  }
  updateCameraRotation(e) {
    e && (this.cameraData.pose.rotation.copy(e), this.cameraData.pose.commit(), this.broadcastCameraMoved({ rotation: e }))
  }
  resetPitch() {
    this.eulerCache.setFromQuaternion(this.cameraData.pose.rotation, "YXZ")
    this.eulerCache.x = 0
    this.eulerCache.z = 0
    this.quaternionCache.setFromEuler(this.eulerCache)
    this.updateCameraRotation(this.quaternionCache)
  }
  updateCameraPosition(e) {
    e && (this.cameraData.pose.position.copy(e), this.cameraData.pose.commit(), this.broadcastCameraMoved({ position: e }))
  }
  broadcastCameraMoved(e) {
    const t = this.fromPose(),
      i = Object.assign(Object.assign({}, t), e)
    this.engine.broadcast(new CameraMoveMessage(i, t))
  }
  updateCameraPose(e) {
    e.position && this.updateCameraPosition(e.position), e.rotation && this.updateCameraRotation(e.rotation)
  }
  canTransition() {
    return this.cameraData.canTransition()
  }
  moveTo(e) {
    if (!this.canTransition()) return OpenDeferred.reject("Cannot move while the camera is already moving")
    const t = b[e.transitionType],
      i = e.pose.rotation,
      n = e.pose.position
    let s = e.projection
    e.pose.zoom && ((s && r.Pp(s)) || this.cameraData.isOrtho()) && (s = this.adjustProjectionZoom(s || this.cameraData.pose.projection.clone(), e.pose.zoom))
    const o = void 0 !== e.blackoutTime ? e.blackoutTime : t.blackoutTime,
      a = e.transitionType,
      l = e.transitionSpeedMultiplier ? 1 / e.transitionSpeedMultiplier : 1
    let c = a === transitionTypeEnum.Instant ? 0 : e.transitionTime
    if (void 0 === c)
      if (e.transitionType === transitionTypeEnum.Interpolate && n) {
        const e = n.distanceTo(this.cameraData.pose.position)
        c = this.baseTransitionTime + Math.log2(1 + e) * configConst.TRANSITION_DISTANCE_MULTIPLIER * this.transitionSpeed * l
      } else c = t.transitionTime
    const d = this.fromPose()
    this.beginInternalTransition({
      transitionType: a,
      transitionTime: c,
      blackoutTime: o,
      pose: { position: n, rotation: i },
      projection: s,
      easing: e.easing,
      rotationDelay: e.rotationDelay || 0,
      rotationDuration: e.rotationDuration || 1,
      matrixDelay: e.matrixDelay || 0,
      matrixDuration: e.matrixDuration || 1,
      focalDistance: e.focalDistance || d.focalDistance
    })
    const u = {
      position: e.pose.position ? e.pose.position.clone() : void 0,
      rotation: e.pose.rotation ? e.pose.rotation.clone() : void 0,
      projection: e.projection ? e.projection.clone() : void 0,
      focalDistance: e.focalDistance ? e.focalDistance : void 0
    }
    this.engine.broadcast(new BeginCameraMoveMessage(u, d))
    const p = this.cameraData.transition.promise
    if (!p) throw new Error("Expected promise to have been created.")
    const m = this.progressInternalTransition.bind(this)
    const y = this.endInternalTransition.bind(this)
    const E = this
    p.notify(0)
    this.engine.startGenerator(function* () {
      let t = 16
      for (t >= E.cameraData.transition.duration && (p.notify(0.5), yield new NullGeneratorResult()); t < E.cameraData.transition.duration; ) {
        m(t, o)
        const e = funcUtil.getMax(t / E.cameraData.transition.duration, 0, 1)
        p.notify(e)
        yield new NullGeneratorResult()
        t = 16 + Date.now() - E.cameraData.transition.startTime
      }
      y(e.transitionType)
      E.cameraData.commit()
      p.notify(1)
      p.resolve()
      E.engine.broadcast(new EndCameraMoveMessage(u, d))
    })
    return p.promise()
  }
  async cancelTransition() {
    return this.cameraData.transition.active
      ? new Promise(e => {
          copyData(this.cameraData.pose, this.cameraData.transition.to, !1)
          const t = Date.now() - this.cameraData.transition.startTime
          ;(this.cameraData.transition.duration = t),
            this.cameraData.transition.progress.stop(1),
            this.cameraData.pose.commit(),
            this.cameraData.transition.commit()
          const i = this.cameraData.transition.onPropertyChanged("active", t => {
            t || (i && i.cancel(), e(null))
          })
        })
      : Promise.resolve()
  }
  updateTransitionSpeed(e) {
    if (this.cameraData.transition.active) {
      const t = Date.now(),
        i = t - this.cameraData.transition.startTime,
        n = i / e,
        s = this.cameraData.transition.progress,
        r = (s.duration - i) / e + n
      ;(this.cameraData.transition.duration = r),
        (this.cameraData.transition.startTime = t - n),
        s.modifyAnimation(s.value, s.endValue, r, s.easing, n),
        this.cameraData.transition.commit()
    }
  }
  beginExternalTransition() {
    if (!this.cameraData.transition.activeInternal) {
      this.cameraData.transition.startTime = Date.now()
      this.cameraData.transition.active = !0
      this.cameraData.transition.promise = new OpenDeferred()
      this.cameraData.transition.commit()
    }
  }
  endExternalTransition() {
    var e
    this.cameraData.transition.activeInternal ||
      ((this.cameraData.transition.active = !1),
      null === (e = this.cameraData.transition.promise) || void 0 === e || e.resolve(),
      this.cameraData.transition.commit())
  }
  beginInternalTransition(e) {
    const t = e.transitionTime || 0
    this.cameraData.transition.startTime = Date.now()
    this.cameraData.transition.duration = t
    this.cameraData.transition.active = !0
    this.cameraData.transition.promise = new OpenDeferred()
    this.cameraData.transition.activeInternal = !0
    this.cameraData.transition.type = e.transitionType
    this.cameraData.transition.progress.modifyAnimation(0, 1, t, e.easing || d.to)
    this.cameraData.transition.rotationDelay = e.rotationDelay || 0
    this.cameraData.transition.rotationDuration = e.rotationDuration || 1
    this.cameraData.transition.matrixDelay = e.matrixDelay || 0
    this.cameraData.transition.matrixDuration = e.matrixDuration || 1
    e.transitionType === transitionTypeEnum.FadeToBlack && this.cameraData.transition.blackoutProgress.modifyAnimation(0, 1, e.blackoutTime, d.vG)
    if (e.transitionType === transitionTypeEnum.MoveToBlack) {
      const e = 500
      this.cameraData.transition.blackoutProgress.modifyAnimation(0, 1, t - 2 * e, d.aB, 0, e)
    }
    this.cameraData.transition.from.position = this.cameraData.pose.position.clone()
    this.cameraData.transition.from.rotation = this.cameraData.pose.rotation.clone()
    this.cameraData.transition.from.projection = this.cameraData.pose.projection.clone()
    this.cameraData.transition.from.focalDistance = this.cameraData.pose.focalDistance
    this.cameraData.transition.to.position = e.pose.position ? e.pose.position.clone() : void 0
    this.cameraData.transition.to.rotation = e.pose.rotation ? e.pose.rotation.clone() : void 0
    this.cameraData.transition.to.projection = e.projection ? e.projection.clone() : void 0
    this.cameraData.transition.to.focalDistance = e.focalDistance ? e.focalDistance : void 0
    this.cameraData.transition.commit()
  }
  progressInternalTransition(e, t) {
    if (this.cameraData.transition.active) {
      if ((this.cameraData.transition.progress.updateAbsolute(e), this.cameraData.transition.type === transitionTypeEnum.Interpolate))
        this.updateInterpolateTransition()
      else if (this.cameraData.transition.type === transitionTypeEnum.FadeToBlack) {
        let i = 0
        const n = this.cameraData.transition.duration
        e <= t ? (i = e) : (copyData(this.cameraData.transition.to, this.cameraData.pose, !1), (i = e >= n - t ? n - e : t)),
          this.cameraData.transition.blackoutProgress.updateAbsolute(i),
          this.cameraData.transition.progress.updateAbsolute(e <= t ? 0 : n)
      } else if (this.cameraData.transition.type === transitionTypeEnum.MoveToBlack) {
        const t = this.cameraData.transition.blackoutProgress
        this.updateInterpolateTransition(), t.updateAbsolute(e - t.delay)
      }
      this.cameraData.pose.commit(), this.cameraData.transition.commit()
    }
  }
  updateInterpolateTransition() {
    const e = this.cameraData.transition.from,
      t = this.cameraData.transition.to
    if (
      (t.position && e.position && this.cameraData.pose.position.copy(e.position).lerp(t.position, this.cameraData.transition.progress.value),
      t.focalDistance &&
        e.focalDistance &&
        (this.cameraData.pose.focalDistance = funcUtil.focalDistance(e.focalDistance, t.focalDistance, this.cameraData.transition.progress.value)),
      t.rotation && e.rotation)
    ) {
      const i = d.e3(
        this.cameraData.transition.progress.easing,
        this.cameraData.transition.progress.value,
        this.cameraData.transition.progress.elapsed,
        this.cameraData.transition.progress.duration * this.cameraData.transition.rotationDuration,
        this.cameraData.transition.rotationDelay
      )
      this.cameraData.pose.rotation.copy(e.rotation).slerp(t.rotation, i)
    }
    if (t.projection && e.projection) {
      const i = d.e3(
        d.w2,
        this.cameraData.transition.progress.value,
        this.cameraData.transition.progress.elapsed,
        this.cameraData.transition.progress.duration * this.cameraData.transition.matrixDuration,
        this.cameraData.transition.matrixDelay
      )

      const s = this.cameraData.pose.projection.elements,
        r = e.projection.elements,
        o = t.projection.elements
      for (let e = 0; e < 16; e++) s[e] = r[e] * (1 - i) + o[e] * i
    }
  }
  endInternalTransition(e) {
    copyData(this.cameraData.transition.to, this.cameraData.pose, !1)
    this.cameraData.transition.type = null
    this.cameraData.transition.active = !1
    this.cameraData.transition.activeInternal = !1
    this.cameraData.transition.progress.stop(1)
    if (!(e !== transitionTypeEnum.FadeToBlack && e !== transitionTypeEnum.MoveToBlack)) {
      this.cameraData.transition.blackoutProgress.stop(0)
    }
    this.cameraData.pose.commit()
    this.cameraData.transition.commit()
  }
  adjustProjectionZoom(e, t) {
    const i = 1 / t
    e.elements[0] = i / this.canvas.aspectRatio
    e.elements[5] = i
    return e
  }
  setBaseProjection(e) {
    this.cameraData.setBaseProjection(e)
  }
}
